import './index.less'

import { createApp } from 'vue'

import DmpLabel from '../../../components/DmpLabel.vue'
import { DEFAULT_CONFIG, DefaultConfig, subLineTemplate, UpdateFunc } from './config'

/**
 * 文本标注
 */
export class TextTriadLabel {
  isExistSubLine: boolean
  showSubLine: boolean
  container: HTMLElement | null = null
  currentTarget: EventTarget | null = null
  config: DefaultConfig
  updateEvent?: UpdateFunc
  constructor(config: DefaultConfig = DEFAULT_CONFIG) {
    this.isExistSubLine = false
    this.showSubLine = config.showSubLine ?? true
    this.config = {
      ...DEFAULT_CONFIG,
      ...config,
      textSelectionStyle: {
        ...DEFAULT_CONFIG.textSelectionStyle,
        ...config.textSelectionStyle
      },
      labelBeforeSpanActiveStyle: {
        ...DEFAULT_CONFIG.labelBeforeSpanActiveStyle,
        ...config.labelBeforeSpanActiveStyle
      }
    }
  }

  /**
   * 初始化
   * @param {HTMLElement} container 容器
   */
  init = (container: HTMLElement) => {
    this.container = container
    // 创建辅助线
    if (this.showSubLine) {
      document.body.appendChild(document.createRange().createContextualFragment(subLineTemplate))
    }
    // 监听鼠标事件
    container.addEventListener('mouseup', this.handleMouseUp)
    container.addEventListener('click', this.handleClick)
    container.addEventListener('contextmenu', this.handleContextMenu)
  }

  /**
   * 右键事件
   * @param e
   */
  handleContextMenu = (e: any) => {
    e.preventDefault()
    this.currentTarget = null
    const target = e.target
    const customData = target?.getAttribute('custom-data')
    if (!customData || customData !== 'TAG') {
      return
    }
    this.currentTarget = target

    // 创建辅助线
    if (this.showSubLine) {
      this.createSubLine(target)
    }
  }

  /**
   * 点击事件
   */
  handleClick = (e: any) => {
    if (this.currentTarget) {
      if (e.target === this.currentTarget) {
        this.updateEvent?.({
          type: 'INFO',
          message: '初始标签和结束标签相同',
          data: null
        })
      } else if (e.target.getAttribute('custom-data') === 'TAG') {
        this.updateEvent?.({
          type: 'SUCCESS',
          message: '标签匹配成功',
          data: e.target
        })
      }
    }
    if (this.showSubLine) {
      const subLine = document.getElementById('sub-line')!
      subLine.setAttribute('data-sub-line-show', 'false')
      this.isExistSubLine = false
    }
    this.currentTarget = null
  }

  /**
   * 鼠标抬起事件
   */
  handleMouseUp = (e: any) => {
    console.log('handleMouseUp', e.target.getAttribute('data-selection'))
    if (e.target && e.target.getAttribute('data-selection')) {
      return
    }
    const selection = window.getSelection()!
    if (!selection) {
      return
    }
    if (this.checkNodeIncluded(selection)) {
      selection.removeAllRanges()
      this.updateEvent?.({
        type: 'INFO',
        message: '选中的文字中包含标签',
        data: null
      })
      return
    }
    const selectionText = selection.toString()
    if (!selectionText) {
      return
    }

    // 将selectionText里面每一个文字都用一个span标签包裹起来
    const characters = selectionText.split('')
    const range = selection.getRangeAt(0)
    if (range.commonAncestorContainer.nodeName === 'DIV') {
      this.updateEvent?.({
        type: 'ERROR',
        message: '选中操作不合法',
        data: null
      })
      return
    }

    // 创建一个新的Vue实例，并传入props数据
    const dmpLabelComponent = createApp(DmpLabel, {
      title: '标题'
    })
    // 创建一个新的元素
    const dmpLabel = document.createElement('div')
    dmpLabel.setAttribute('class', 'label-text-tag-wrap')
    // 挂载到新的元素上
    dmpLabelComponent.mount(dmpLabel)

    // 创建一个文档片段
    const fragment = document.createDocumentFragment()
    // 遍历每个字符，为其创建一个 <span> 元素并包裹起来
    characters.forEach(function (character) {
      const span = document.createElement('span')
      span.textContent = character
      Object.assign(span.style, DEFAULT_CONFIG.textSelectionStyle)
      fragment.appendChild(span)
    })

    // const space = document.createElement('div')
    // space.setAttribute('class', 'label-text-tag')
    // space.setAttribute('custom-data', 'TAG')
    // space.textContent = '测试标签'
    fragment.appendChild(dmpLabel)
    // 用包含了 <span> 元素的文档片段替换选中的内容
    range.deleteContents()
    range.insertNode(fragment)
    // 不选中
    selection.removeAllRanges()

    // 监听鼠标移入事件
    dmpLabel.addEventListener('mouseenter', () => {
      const labelBeforeSpanAddStyle = (element: any) => {
        if (element?.nodeName === 'SPAN') {
          Object.assign(element.style, this.config.labelBeforeSpanActiveStyle)
          labelBeforeSpanAddStyle(element.previousElementSibling!)
          return
        }
      }
      labelBeforeSpanAddStyle(dmpLabel.previousElementSibling)
    })
    // 监听鼠标移出事件
    dmpLabel.addEventListener('mouseleave', () => {
      this.container?.querySelectorAll('span').forEach(span => {
        span.style.background = 'transparent'
      })
    })
  }

  /**
   * 检查选取的文字中是否存在某个节点
   * @param selection
   * @param nodeName
   * @returns {boolean}
   */
  checkNodeIncluded = (selection: Selection, nodeName = 'SPAN'): boolean => {
    if (selection.rangeCount === 0) {
      return false
    }
    const range = selection.getRangeAt(0)
    const el = range.cloneContents()
    const nodes = el.childNodes || []
    let isExistSpan = false
    nodes.forEach(node => {
      if (node.nodeName === nodeName.toUpperCase()) {
        isExistSpan = true
      }
    })
    return isExistSpan
  }

  /**
   * 创建辅助线
   * @param target 基于哪个元素创建辅助线
   */
  createSubLine = (target: any) => {
    const subLine = document.getElementById('sub-line')!
    const left = target.offsetWidth / 2 - subLine.offsetWidth / 2
    const top = 0
    subLine.style.left = left + 'px'
    subLine.style.top = top + 'px'

    // 监听鼠标移动事件
    const mousemove = (e: any) => {
      if (!this.isExistSubLine || this.container === null) {
        return
      }
      if (e.target === this.currentTarget) {
        subLine.setAttribute('data-sub-line-show', 'false')
        return
      }
      const targetRect = target.getBoundingClientRect()

      const subLineTop = targetRect.top
      let clientX = e.pageX || e.clientX + scrollX
      let clientY = e.pageY || e.clientY + scrollY
      const containerRect = this.container.getBoundingClientRect()

      // 不能超出context右边界
      if (clientX > containerRect.left + containerRect.width) {
        clientX = Math.min(clientX, containerRect.left + containerRect.width)
      }
      // 不能超出context左边界
      if (clientX < containerRect.left) {
        clientX = Math.max(clientX, containerRect.left)
      }
      // 不能超出context下边界
      if (clientY > containerRect.top + containerRect.height) {
        clientY = Math.min(clientY, containerRect.top + containerRect.height)
      }
      // 不能超出context上边界
      if (clientY < containerRect.top) {
        clientY = Math.max(clientY, containerRect.top)
      }

      const subLineHeight = Math.abs(clientY - subLineTop)
      const baseW = Math.abs(targetRect.left + targetRect.width / 2 - clientX)
      // 计算斜边长度 (baseW的平方 + subLineHeight的平方)开根
      const hypotenuse = Math.sqrt(baseW ** 2 + subLineHeight ** 2)
      subLine.style.height = hypotenuse + 'px'
      // 根据鼠标位置计算旋转角度 保持与鼠标位置一致
      const subLineLeft = targetRect.left + targetRect.width / 2
      const subLineWidth = Math.abs(clientX - subLineLeft)
      let subLineDeg = Math.atan(subLineWidth / subLineHeight) * (180 / Math.PI)
      // 当鼠标在第一象限 处理角度
      if (clientX > subLineLeft && clientY < subLineTop) {
        subLineDeg = 180 + subLineDeg
      }
      // 当鼠标在第二象限 处理角度
      else if (clientX < subLineLeft && clientY < subLineTop) {
        subLineDeg = 180 - subLineDeg
      }
      // 当鼠标在第三象限 处理角度
      else if (clientX < subLineLeft && clientY > subLineTop) {
        subLineDeg = +subLineDeg
      }
      // 当鼠标在第四象限 处理角度
      else if (clientX > subLineLeft && clientY > subLineTop) {
        subLineDeg = -subLineDeg
      }
      const subLineBoxWidth = subLine.offsetWidth
      const subLinePath = subLine.querySelector('#sub-line-path')!
      if (clientX < subLineLeft) {
        // 当鼠标在左侧时
        subLinePath.setAttribute(
          'd',
          `M ${subLineBoxWidth / 2} 0 Q -${subLineBoxWidth / 2} ${hypotenuse / 2} ${subLineBoxWidth / 2} ${hypotenuse}`
        )
      } else {
        // 当鼠标在右侧时
        subLinePath.setAttribute(
          'd',
          `M ${subLineBoxWidth / 2} 0 Q ${subLineBoxWidth + subLineBoxWidth / 2} ${hypotenuse / 2} ${subLineBoxWidth / 2} ${hypotenuse}`
        )
      }
      subLine.style.transform = `rotate(${subLineDeg}deg)`
      subLine.setAttribute('data-sub-line-show', 'true')
    }

    if (this.isExistSubLine) {
      subLine.setAttribute('data-sub-line-show', 'false')
      this.isExistSubLine = false
      this.currentTarget = null
      document.removeEventListener('mousemove', mousemove)
      return
    }
    if (this.showSubLine) {
      document.addEventListener('mousemove', mousemove)
      target.appendChild(subLine)
      this.isExistSubLine = true
    }
  }

  /**
   * 监听状态更新
   */
  update = (callback: UpdateFunc) => {
    this.updateEvent = callback
  }

  /**
   * 销毁
   */
  destroy = () => {
    this.container?.removeEventListener('mouseup', this.handleMouseUp)
    this.container?.removeEventListener('click', this.handleClick)
    this.container?.removeEventListener('contextmenu', this.handleContextMenu)
    this.container = null
    this.updateEvent = undefined
  }
}
